slimecing

a fighting game featuring slimes and swords
Log | Files | Refs | README

CustomComposite.cs (8529B)


      1 using UnityEngine;
      2 using UnityEngine.InputSystem;
      3 using UnityEngine.InputSystem.Layouts;
      4 using UnityEngine.InputSystem.Utilities;
      5 
      6 #if UNITY_EDITOR
      7 using UnityEditor;
      8 using UnityEngine.InputSystem.Editor;
      9 #endif
     10 
     11 // Let's say we want to have a composite that takes an axis and uses
     12 // it's value to multiply the length of a vector from a stick. This could
     13 // be used, for example, to have the right trigger on the gamepad act as
     14 // a strength multiplier on the value of the left stick.
     15 //
     16 // We start by creating a class that is based on InputBindingComposite<>.
     17 // The type we give it is the type of value that we will compute. In this
     18 // case, we will consume a Vector2 from the stick so that is the type
     19 // of value we return.
     20 //
     21 // NOTE: By advertising the type of value we return, we also allow the
     22 //       input system to filter out our composite if it is not applicable
     23 //       to a specific type of action. For example, if an action is set
     24 //       to "Value" as its type and its "Control Type" is set to "Axis",
     25 //       our composite will not be shown as our value type (Vector2) is
     26 //       incompatible with the value type of Axis (float).
     27 //
     28 // Also, we need to register our composite with the input system. And we
     29 // want to do it in a way that makes the composite visible in the action
     30 // editor of the input system.
     31 //
     32 // For that to happen, we need to call InputSystem.RegisterBindingComposite
     33 // sometime during startup. We make that happen by using [InitializeOnLoad]
     34 // in the editor and [RuntimeInitializeOnLoadMethod] in the player.
     35 #if UNITY_EDITOR
     36 [InitializeOnLoad]
     37 #endif
     38 // We can customize the way display strings are formed for our composite by
     39 // annotating it with DisplayStringFormatAttribute. The string is simply a
     40 // list with elements to be replaced enclosed in curly braces. Everything
     41 // outside those will taken verbatim. The fragments inside the curly braces
     42 // in this case refer to the binding composite parts by name. Each such
     43 // instance is replaced with the display text for the corresponding
     44 // part binding.
     45 [DisplayStringFormat("{multiplier}*{stick}")]
     46 public class CustomComposite : InputBindingComposite<Vector2>
     47 {
     48     // In the editor, the static class constructor will be called on startup
     49     // because of [InitializeOnLoad].
     50     #if UNITY_EDITOR
     51     static CustomComposite()
     52     {
     53         // Trigger our RegisterBindingComposite code in the editor.
     54         Initialize();
     55     }
     56 
     57     #endif
     58 
     59     // In the player, [RuntimeInitializeOnLoadMethod] will make sure our
     60     // initialization code gets called during startup.
     61     [RuntimeInitializeOnLoadMethod]
     62     private static void Initialize()
     63     {
     64         // This registers the composite with the input system. After calling this
     65         // method, we can have bindings reference the composite. Also, the
     66         // composite will show up in the action editor.
     67         //
     68         // NOTE: We don't supply a name for the composite here. The default logic
     69         //       will take the name of the type ("CustomComposite" in our case)
     70         //       and snip off "Composite" if used as a suffix (which is the case
     71         //       for us) and then use that as the name. So in our case, we are
     72         //       registering a composite called "Custom" here.
     73         //
     74         //       If we were to use our composite with the AddCompositeBinding API,
     75         //       for example, it would look like this:
     76         //
     77         //       myAction.AddCompositeBinding("Custom")
     78         //           .With("Stick", "<Gamepad>/leftStick")
     79         //           .With("Multiplier", "<Gamepad>/rightTrigger");
     80         InputSystem.RegisterBindingComposite<CustomComposite>();
     81     }
     82 
     83     // So, we need two parts for our composite. The part that delivers the stick
     84     // value and the part that delivers the axis multiplier. Note that each part
     85     // may be bound to multiple controls. The input system handles that for us
     86     // by giving us an integer identifier for each part that reads a single value
     87     // from however many controls are bound to the part.
     88     //
     89     // In our case, this could be used, for example, to bind the "multiplier" part
     90     // to both the left and the right trigger on the gamepad.
     91 
     92     // To tell the input system of a "part" binding that we need for a composite,
     93     // we add a public field with an "int" type and annotated with an [InputControl]
     94     // attribute. We set the "layout" property on the attribute to tell the system
     95     // what kind of control we expect to be bound to the part.
     96     //
     97     // NOTE: These part binding need to be *public fields* for the input system
     98     //       to find them.
     99     //
    100     // So this is introduces a part to the composite called "multiplier" and
    101     // expecting an "Axis" control. The value of the field will be set by the
    102     // input system. It will be some internal, unique numeric ID for the part
    103     // which we can then use with InputBindingCompositeContext.ReadValue to
    104     // read out the value of just that part.
    105     [InputControl(layout = "Axis")]
    106     public int multiplier;
    107 
    108     // The other part we need is for the stick.
    109     //
    110     // NOTE: We could use "Stick" here but "Vector2" is a little less restrictive.
    111     [InputControl(layout = "Vector2")]
    112     public int stick;
    113 
    114     // We may also expose "parameters" on our composite. These can be configured
    115     // graphically in the action editor and also through AddCompositeBinding.
    116     //
    117     // Let's say we want to allow the user to specify an additional scale factor
    118     // to apply to the value of "multiplier". We can do so by simply adding a
    119     // public field of type float. Any public field that is not annotated with
    120     // [InputControl] will be treated as a possible parameter.
    121     //
    122     // If we added a composite with AddCompositeBinding, we could configure the
    123     // parameter like so:
    124     //
    125     //     myAction.AddCompositeBinding("Custom(scaleFactor=0.5)"
    126     //         .With("Multiplier", "<Gamepad>/rightTrigger")
    127     //         .With("Stick", "<Gamepad>/leftStick");
    128     public float scaleFactor = 1;
    129 
    130     // Ok, so now we have all the configuration in place. The final piece we
    131     // need is the actual logic that reads input from "multiplier" and "stick"
    132     // and computes a final input value.
    133     //
    134     // We can do that by defining a ReadValue method which is the actual workhorse
    135     // for our composite.
    136     public override Vector2 ReadValue(ref InputBindingCompositeContext context)
    137     {
    138         // We read input from the parts we have by simply
    139         // supplying the part IDs that the input system has set up
    140         // for us to ReadValue.
    141         //
    142         // NOTE: Vector2 is a less straightforward than primitive value types
    143         //       like int and float. If there are multiple controls bound to the
    144         //       "stick" part, we need to tell the input system which one to pick.
    145         //       We do so by giving it an IComparer. In this case, we choose
    146         //       Vector2MagnitudeComparer to return the Vector2 with the greatest
    147         //       length.
    148         var stickValue = context.ReadValue<Vector2, Vector2MagnitudeComparer>(stick);
    149         var multiplierValue = context.ReadValue<float>(multiplier);
    150 
    151         // The rest is simple. We just scale the vector we read by the
    152         // multiple from the axis and apply our scale factor.
    153         return stickValue * (multiplierValue * scaleFactor);
    154     }
    155 }
    156 
    157 // Our custom composite is complete and fully functional. We could stop here and
    158 // call it a day. However, for the sake of demonstration, let's say we also want
    159 // to customize how the parameters for our composite are edited. We have "scaleFactor"
    160 // so let's say we want to replace the default float inspector with a slider.
    161 //
    162 // We can replace the default UI by simply deriving a custom InputParameterEditor
    163 // for our composite.
    164 #if UNITY_EDITOR
    165 public class CustomCompositeEditor : InputParameterEditor<CustomComposite>
    166 {
    167     public override void OnGUI()
    168     {
    169         // Using the 'target' property, we can access an instance of our composite.
    170         var currentValue = target.scaleFactor;
    171 
    172         // The easiest way to lay out our UI is to simply use EditorGUILayout.
    173         // We simply assign the changed value back to the 'target' object. The input
    174         // system will automatically detect a change in value.
    175         target.scaleFactor = EditorGUILayout.Slider(m_ScaleFactorLabel, currentValue, 0, 2);
    176     }
    177 
    178     private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor");
    179 }
    180 #endif